<template>
    <!-- 表单,支持el-form所有配置，事件-->
    <el-form ref="form" v-bind="$attrs" v-on="$listeners" :model="formData">
        <template v-for="item in finalFields">
            <slot
                v-if="item.slot"
                :name="item.slot"
                v-bind="{ formData, item }"
            ></slot>
            <template v-else>
                <div :key="item.field" class="item-el" v-show="!item.hidden">
                    <el-form-item
                        :prop="item.field"
                        :class="item.cls"
                        :value="item.value"
                    >
                        <span slot="label" v-html="item.label"></span>
                        <component
                            :is="item.componentType"
                            v-bind="item.options"
                            v-model="formData[item.field]"
                        >
                            <template v-if="item.fieldType == 'select'">
                                <el-option
                                    v-for="data in optionData[item.field]"
                                    :key="data.value"
                                    :label="data.label"
                                    :value="data.value"
                                ></el-option>
                            </template>
                        </component>
                    </el-form-item>
                </div>
            </template>
        </template>
    </el-form>
</template>

<script>
import { find, defaultsDeep, cloneDeep, assign, set, get, pick } from "lodash";
import { getListByConfig, clearObject } from "@/utils";
export default {
    name: "UxForm",
    props: {
        // 组件插槽列表
        slotList: {
            type: Array,
            default: function () {
                return [];
            }
        },
        // 表单组件配置
        fields: {
            type: Array,
            default: function () {
                return [];
            }
        },
        // 表单组件通用配置
        componentConfig: {
            type: Object,
            default: function () {
                return {};
            }
        }
    },
    data() {
        return {
            formData: {},
            // 下拉数据
            optionData: {},
            // 表单最终配置
            finalFields: [],
            // 组件默认配置
            componentDefaultConfig: {
                clearable: true
            },
            // 组件类型映射关系
            // 对不同组件类型可以配置不同的默认配置
            typeMap: [
                {
                    fieldType: "input",
                    componentType: "el-input",
                    placeholder: "请输入"
                },
                {
                    fieldType: "select",
                    componentType: "el-select",
                    placeholder: "请选择"
                },
                {
                    fieldType: "date",
                    componentType: "el-date-picker",
                    placeholder: "请选择",
                    valueFormat: "yyyy-MM-dd"
                },
                {
                    fieldType: "daterange",
                    componentType: "el-date-picker",
                    type: "daterange",
                    startPlaceholder: "开始时间",
                    endPlaceholder: "结束时间",
                    valueFormat: "yyyy-MM-dd"
                },
                {
                    fieldType: "cascader",
                    componentType: "el-cascader",
                    placeholder: "请选择",
                    isCascader: true,
                    options: []
                }
            ],
            defaultValues: null
        };
    },
    computed: {
        // 表单组件最终通用配置
        finalComponentConfig() {
            return defaultsDeep(
                {},
                this.componentConfig,
                this.componentDefaultConfig
            );
        }
    },
    watch: {
        // 监听表单配置组，动态生成相关配置
        fields: {
            immediate: true,
            handler(v) {
                // 将表单配置转化为标准配置
                const me = this,
                    // 组件类型映射关系
                    typeMap = me.typeMap,
                    // 组件最终通用配置
                    finalComponentConfig = me.finalComponentConfig,
                    // 初始组件配置组
                    fields = [],
                    // 组件数据源
                    fieldsOptionData = {},
                    // 组件数据
                    formData = {},
                    slotList = [];
                v.forEach((item) => {
                    const {
                            // 表单类型（用于标识判断）
                            fieldType = "input"
                        } = item,
                        // 组件最终通用配置
                        data = defaultsDeep(
                            // 组件配置（这里的配置会覆盖后面的配置）
                            item,
                            // 组件最终通用配置
                            finalComponentConfig,
                            // 根据组件类型映射关系获取表单默认配置
                            find(typeMap, { fieldType })
                        ),
                        {
                            // 表单name
                            field,
                            // 插槽
                            slot,
                            // 组件类型
                            componentType,
                            // 表单label
                            label,
                            // 是否隐藏表单
                            hidden,
                            // 样式
                            cls,
                            // 数据
                            optionData = [],
                            fieldConfig,
                            isCascader,
                            value = "",
                            // 组件其他配置
                            ...options
                        } = data;
                    // 如果是下拉组件
                    if (fieldType == "select") {
                        // 生成默认下拉值，避免双向绑定失效
                        fieldsOptionData[field] = me.transformDataByConfig(
                            fieldConfig,
                            optionData
                        );
                    }
                    // console.log(field, value);
                    // 设置默认值，避免双向绑定失效
                    set(formData, field, value);
                    fields.push({
                        fieldType,
                        slot,
                        componentType,
                        field,
                        label,
                        hidden,
                        cls,
                        fieldConfig,
                        isCascader,
                        options
                    });
                    if (slot) {
                        slotList.push(slot);
                    }
                });
                me.$emit("update:slotList", slotList);
                // console.log("formData", formData);
                // 更新表单默认值
                // me.$emit("updateValue", assign(values, me.values));
                me.formData = assign(formData, me.formData);
                // 设置组件数据
                me.optionData = fieldsOptionData;
                // console.log("optionData", me.optionData);
                // 设置最终表单组件配置
                me.finalFields = fields;
                // 清除验证规则，避免首次加载出现错误提示
                me.clearValidate();
            }
        },
        formData: {
            deep: true,
            handler(v) {
                this.$emit("update", v);
            }
        }
    },
    methods: {
        /**
         * 获取表单对象
         */
        getForm() {
            return this.$refs.form;
        },
        /**
         * 更新表单数据
         * @param {Object} v 数据
         * @export Promise
         *
         */
        updateFormData(v) {
            const me = this;
            const values = assign(me.formData, v);
            me.formData = values;
        },
        /**
         * 根据字段名称设置数据（数据会根据fieldConfig配置自动进行转化）
         * @param {string} field 字段名称
         * @param {Array<any>} data 数据
         */
        setFieldData(field, data) {
            // 根据字段名称获取配置
            const me = this,
                item = find(me.finalFields, { field });
            // console.log(item);
            if (item) {
                if (item.isCascader) {
                    // 级联组件设置值
                    me.setFieldConfig(field, "options.options", data);
                } else {
                    // 下拉组件设置值
                    me.optionData[field] = me.transformDataByConfig(
                        item.fieldConfig,
                        data
                    );
                }
            }
        },
        /**
         * 根据数据转换配置转化数据
         * @param {any} config 数据转换配置
         * @param {Array<any>} data 数据
         * @export 新数据
         *
         */
        transformDataByConfig(config, data) {
            // 获取数据转化配置
            const fieldConfig = defaultsDeep({}, config, {
                // 数据名称
                label: "label",
                // 数据值
                value: "value"
            });
            // 返回转化后的值
            return getListByConfig(data, fieldConfig, {
                stringList: ["label", "value"]
            });
        },
        /**
         * 根据字段名称设置表单组件配置
         * @param {string} field 字段名称
         * @param {string} path 配置路径
         * @param {string} value 配置值
         */
        setFieldConfig(field, path, value) {
            const item = find(this.finalFields, { field });
            if (item) {
                // 设置值
                set(item, path, value);
            }
        },
        /**
         * 根据字段名称切换组件配置（只能是bool类型的配置）
         * @param {string} field 字段名称
         * @param {string} path 配置路径
         */
        toggleFieldConfig(field, path) {
            const item = find(this.finalFields, { field });
            if (item) {
                const is = get(item, path);
                // 设置值
                set(item, path, !is);
            }
        },
        /**
         * 根据字段名称批量设置组件值
         * @param {Array<string>} fields 字段名称数组
         * @param {string} path 配置路径
         * @param {any} is 配置值
         * @param {boole} changeOther 当配置值为boole类型时，是否将其他组件值设为相反值
         */
        setFieldsConfig(fields, path, is, changeOther = false) {
            this.finalFields.forEach((item) => {
                if (fields.includes(item.field)) {
                    set(item, path, is);
                } else if (changeOther) {
                    set(item, path, !is);
                }
            });
        },
        /**
         * 根据字段名称批量设置组件是否隐藏
         * @param {Array<string>} fields 字段名称数组
         * @param {boole} is 是否隐藏
         * @param {boole} changeOther  其他组件是否设为相反状态
         */
        setFieldsHidden(fields, is, changeOther = false) {
            this.setFieldsConfig(fields, "hidden", is, changeOther);
        },
        /**
         * 根据字段名称设置组件是否隐藏
         * @param {string} field 字段名称
         * @param {boole} is 是否隐藏
         */
        setFieldHidden(field, is) {
            this.setFieldConfig(field, "hidden", is);
        },
        /**
         * 根据字段名称切换组件隐藏状态
         * @param {string} field 字段名称
         */
        toggleFieldHidden(field) {
            this.toggleFieldConfig(field, "hidden");
        },
        /**
         * 根据字段名称显示组件
         * @param {string} field 字段名称
         */
        showField(field) {
            this.setFieldHidden(field, false);
        },
        /**
         * 根据字段名称隐藏组件
         * @param {string} field 字段名称
         */
        hiddenField(field) {
            this.setFieldHidden(field, true);
        },
        /**
         * 根据字段名称批量设置组件是否禁用
         * @param {Array<string>} fields 字段名称数组
         * @param {boole} is 是否禁用
         * @param {boole} changeOther 其他组件是否设为相反状态
         */
        setFieldsDisabled(fields, is, changeOther = false) {
            this.setFieldsConfig(fields, "options.disabled", is, changeOther);
        },
        /**
         * 根据字段名称设置组件是否禁用
         * @param {string} field 字段名称
         * @param {boole} is 是否禁用
         */
        setFieldDisabled(field, is) {
            this.setFieldConfig(field, "options.disabled", is);
        },
        /**
         * 验证表单（只验证显示的表单组件）
         * 验证成功会返回表单值
         * @param {*} [{
         *  isclearEmpty = true 表单值是否清除空数据
         *  isAll = false 是否返回所有表单值（false时只返回显示表单组件的值）
         *  }={}]
         * @export Promise
         *
         */
        validateForm({ isAll = false, isclearEmpty = true } = {}) {
            return new Promise((resolve, reject) => {
                const me = this,
                    fields = me.finalFields,
                    validateList = [];
                // 循环遍历获取显示的表单组件
                fields.forEach(({ hidden, field }) => {
                    if (!hidden) {
                        validateList.push(field);
                    }
                });
                // 验证结果
                let is = true;
                // 验证指定字段
                me.getForm().validateField(validateList, function (v) {
                    // 验证错误是v有值
                    v && (is = false);
                });
                // console.log(me.values);
                if (is) {
                    // 拷贝表单数据
                    let values = cloneDeep(me.formData);
                    if (isclearEmpty) {
                        // 清除空数据
                        values = clearObject(values);
                    }
                    // 返回表单值
                    resolve(isAll ? values : pick(values, validateList));
                } else {
                    reject();
                }
            });
        },
        /**
         * 移除表单项的校验结果。传入待移除的表单项的 prop 属性或者 prop 组成的数组，如不传则移除整个表单的校验结果
         */
        clearValidate(props) {
            const me = this;
            me.$nextTick(() => {
                me.getForm().clearValidate(props);
            });
        },
        /**
         * 对整个表单进行重置，将所有字段值重置为初始值并移除校验结果
         */
        resetFields() {
            const me = this;
            me.getForm().resetFields();
            const defaultValues = me.defaultValues;
            if (defaultValues) {
                // console.log('resetFields',defaultValues)
                me.updateFormData(defaultValues);
            }
        },
        // /**
        //  * 清空表单值
        //  * #test 待测试
        //  */
        // clearFields() {
        //   const me = this,
        //     values = {};
        //   // 循环遍历清空表单数据
        //   me.finalFields.forEach(({ field }) => {
        //     set(values, field);
        //   });
        //   // 更新表单数据
        //   me.$emit("updateValue", values);
        //   // 清除验证规则，避免出现错误提示
        //   me.clearValidate();
        // },
        /**
         * 设置默认值
         * @param {Object} v 默认值
         * @export Promise
         *
         */
        setDefaultValues(v) {
            this.defaultValues = cloneDeep(v);
            // console.log("setDefaultValues", v);
            this.updateFormData(v);
        }
    }
};
</script>
